Modeling
Before jumping into these problems, you should read through (and follow along with!) the model stacking and global model interpretation tutorials on the Course Materials tab of the course website.
We’ll be using the lending_club dataset from the modeldata library, which is part of tidymodels. The data dictionary they reference doesn’t seem to exist anymore, but it seems the one on this kaggle discussion is pretty close. It might also help to read a bit about Lending Club before starting in on the exercises.
The outcome we are interested in predicting is Class. And according to the dataset’s help page, its values are “either ‘good’ (meaning that the loan was fully paid back or currently on-time) or ‘bad’ (charged off, defaulted, of 21-120 days late)”.
Tasks: I will be expanding these, but this gives a good outline.
- Explore the data, concentrating on examining distributions of variables and examining missing values.
There are no NA values in the data.
lending_club %>%
select(everything()) %>%
summarise_all(funs(sum(is.na(.))))
It looks like a majority of the numeric variables are right skewed. I can see why Lisa decided to add more ‘bad’ cases to the data.
lending_club %>%
keep(is.numeric) %>%
gather() %>%
ggplot(aes(value)) +
facet_wrap(~ key, scales = "free") +
geom_histogram()

lending_club %>%
select(where(is.factor)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_bar() +
facet_wrap(vars(variable),
scales = "free",
nrow = 2)

- Do any data cleaning steps that need to happen before the model is build. For example, you might remove any variables that mean the same thing as the response variable (not sure if that happens here), get rid of rows where all variables have missing values, etc.
lending_club <-
lending_club %>%
select(-delinq_amnt, -acc_now_delinq)
Be sure to add more “bad” Classes. This is not the best solution, but it will work for now. (Should investigate how to appropriately use step_sample_up() function from themis).
set.seed(494)
create_more_bad <- lending_club %>%
filter(Class == "bad") %>%
sample_n(size = 3000, replace = TRUE)
lending_club_mod <- lending_club %>%
bind_rows(create_more_bad)
- Split the data into training and test, putting 75% in the training data.
set.seed(494) # for reproducibility
lending_split <- initial_split(lending_club_mod,
prop = .75, strata = Class)
lending_training <- training(lending_split)
lending_testing <- testing(lending_split)
- Set up the recipe and the pre-processing steps to build a lasso model. Some steps you should take:
- Make all integer variables numeric (I’d highly recommend using
step_mutate_at() or this will be a lot of code). We’ll want to do this for the model interpretation we’ll do later.
- Think about grouping factor variables with many levels.
- Make categorical variables dummy variables (make sure NOT to do this to the outcome variable).
- Normalize quantitative variables.
lending_recipe <-
recipe(formula = Class ~ .,
data = lending_training) %>%
step_mutate_at(all_numeric(), fn = ~as.numeric(.)) %>%
step_mutate(verification_status = as.factor(ifelse(verification_status == "Not_Verified", "Not_Verified", "Verified")),
sub_grade = fct_collapse(sub_grade,
A = c("A1", "A2", "A3", "A4", "A5"),
B = c("B1", "B2", "B3", "B4", "B5"),
C = c("C1", "C2", "C3", "C4", "C5"),
D = c("D1", "D2", "D3", "D4", "D5"),
E = c("E1", "E2", "E3", "E4", "E5"),
f = c("F1", "F2", "F3", "F4", "F5"),
G = c("G1", "G2", "G3", "G4", "G5")),
addr_state = fct_collapse(addr_state,
Midwest = c("ND", "SD", "NE", "KS", "MN", "WI", "IL", "IN", "MI", "OH", "MO"),
South = c("OK", "TX", "AR", "LA", "MS", "AL", "GA", "FL", "TN", "KY", "SC", "NC", "VA", "WV", "MD", "DE", "DC"),
Northeast = c("NY", "PA", "NJ", "CT", "VT", "MA", "NH", "ME", "RI"),
West = c("WA", "OR", "CA", "MT", "ID", "WY", "NV", "UT", "AZ", "CO", "NM"),
Pacific = c("HI"))) %>%
step_dummy(all_nominal(),
-all_outcomes()) %>%
step_normalize(all_numeric())
lending_recipe %>%
prep(lending_training) %>%
juice()
- Set up the lasso model and workflow. We will tune the
penalty parameter.
lending_lasso_mod <-
logistic_reg(mixture = 1) %>%
set_engine("glmnet") %>%
set_args(penalty = tune()) %>%
set_mode("classification")
lending_lasso_wf <-
workflow() %>%
add_recipe(lending_recipe) %>%
add_model(lending_lasso_mod)
penalty_grid <- grid_regular(penalty(),
levels = 10)
- Set up the model tuning for the
penalty parameter. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Find the accuracy and area under the roc curve for the model with the best tuning parameter. Use 5-fold cv.
Penalty = 4.641589e-04, accuracy = 0.7483136, roc_auc = 0.7589758
set.seed(494) #for reproducible 5-fold
lending_cv <- vfold_cv(lending_training, v = 5)
ctrl_grid <- control_stack_grid()
metric <- metric_set(accuracy)
lending_lasso_tune <-
lending_lasso_wf %>%
tune_grid(
resamples = lending_cv,
grid = penalty_grid,
control = ctrl_grid
)
lending_lasso_tune%>%
collect_metrics()
- Set up the recipe and the pre-processing steps to build a random forest model. You shouldn’t have to do as many steps. The only step you should need to do is making all integers numeric.
lending_rf_recipe <-
recipe(formula = Class ~ .,
data = lending_training) %>%
step_mutate_at(all_numeric(), fn = ~as.numeric(.))
- Set up the random forest model and workflow. We will tune the
mtry and min_n parameters and set the number of trees, trees, to 100 (otherwise the next steps take too long).
lending_rf_spec <-
rand_forest(mtry = tune(),
min_n = tune(),
trees = 100) %>%
set_mode("classification") %>%
set_engine("ranger")
lending_rf_wf <-
workflow() %>%
add_recipe(lending_rf_recipe) %>%
add_model(lending_rf_spec)
- Set up the model tuning for both the
mtry and min_n parameters. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Use only 3 levels in the grid. For the mtry parameter, you need to put finalize(mtry(), lending_training %>% select(-Class)) in as an argument instead of just mtry(), where lending_training is the name of your training data. This is because the mtry() grid will otherwise have unknowns in it. This part can take a while to run.
mtry_grid <- grid_regular(finalize(mtry(), lending_training %>% select(-Class)), min_n(),
levels = 3)
lending_rf_tune <-
lending_rf_wf %>%
tune_grid(
resamples = lending_cv,
grid = mtry_grid,
control = ctrl_grid
)
- Find the best tuning parameters. What is the are the accuracy and area under the ROC curve for the model with those tuning parameters?
The metric with the highest accuracy is mtry = 10 and min_n = 2, with an accuracy of 0.9928452 and an area under the ROC curve of 0.9972089.
lending_rf_tune%>%
collect_metrics()
EXTRA: Set up the finalized random forest model and workflow, then fit the finalized model. (for question 11)
final_rf_spec <-
rand_forest(mtry = 10,
min_n = 2,
trees = 100) %>%
set_mode("classification") %>%
set_engine("ranger")
final_rf_wf <-
workflow() %>%
add_recipe(lending_rf_recipe) %>%
add_model(final_rf_spec)
rf_fit <- final_rf_wf %>%
fit(lending_training)
EXTRA:
ctrl_res <- control_stack_resamples()
ranger_cv <- final_rf_wf %>%
fit_resamples(lending_cv,
control = ctrl_res)
collect_metrics(ranger_cv)
- Use functions from the
DALEX and DALEXtra libraries to create a histogram and boxplot of the residuals from the training data. How do they look? Any interesting behavior?
The residuals are skewed to the right, although its mode is at 0 the mean residual would be greater than 0 (which can be seen in the boxplots).
rf_explain <-
explain_tidymodels(
model = rf_fit,
data = lending_training %>% select(-Class),
y =lending_training %>%
mutate(Class_num = as.integer(Class =="good")) %>%
pull(Class_num),
label = "rf"
)
## Preparation of a new explainer is initiated
## -> model label : rf
## -> data : 9643 rows 20 cols
## -> data : tibble converted into a data.frame
## -> target variable : 9643 values
## -> predict function : yhat.workflow will be used ( [33m default [39m )
## -> predicted values : No value for predict function target column. ( [33m default [39m )
## -> model_info : package tidymodels , ver. 0.1.2 , task classification ( [33m default [39m )
## -> predicted values : numerical, min = 0 , mean = 0.703067 , max = 1
## -> residual function : difference between y and yhat ( [33m default [39m )
## -> residuals : numerical, min = -0.45 , mean = 0.02336669 , max = 0.26
## [32m A new explainer has been created! [39m
rf_mod_perf <- model_performance(rf_explain)
hist_plot <-
plot(rf_mod_perf,
rf_mod_perf,
geom = "histogram")
box_plot <-
plot(rf_mod_perf,
rf_mod_perf,
geom = "boxplot")
hist_plot

box_plot

- Use
DALEX functions to create a variable importance plot from this model. What are the most important variables?
The int_rate, annuak_inc, all_util, sub_grade, revol_util, and emp_length are the most important variables in the random forest model.
set.seed(494)
rf_var_imp <-
model_parts(
rf_explain
)
plot(rf_var_imp, show_boxplots = TRUE)

- Write a function called
cp_profile to make a CP profile. The function will take an explainer, a new observation, and a variable name as its arguments and create a CP profile for a quantitative predictor variable. You will need to use the predict_profile() function inside the function you create - put the variable name there so the plotting part is easier. You’ll also want to use aes_string() rather than aes() and quote the variables. Use the cp_profile() function to create one CP profile of your choosing. Be sure to choose a variable that is numeric, not integer. There seem to be issues with those that I’m looking into.
cp_profile <- function(explainer, new_obs, var) {
cp <-
predict_profile(explainer = explainer,
new_observation = new_obs, variables = var)
cp %>%
rename(yhat = `_yhat_`) %>%
ggplot(aes_string(x = var,
y = "yhat")) +
geom_line()
}
ob <-
lending_testing %>%
slice(4)
cp_profile(rf_explain, ob, "int_rate")

For an extra challenge, write a function that will work for either a quantitative or categorical variable.
If you need help with function writing check out the Functions chapter of R4DS by Wickham and Grolemund.
- Use
DALEX functions to create partial dependence plots (with the CP profiles in gray) for the 3-4 most important variables. If the important variables are categorical, you can instead make a CP profile for 3 observations in the dataset and discuss how you could go about constructing a partial dependence plot for a categorical variable (you don’t have to code it, but you can if you want an extra challenge). If it ever gives you an error that says, “Error: Can’t convert from VARIABLE to VARIABLE due to loss of precision”, then remove that variable from the list. I seem to have figured out why it’s doing that, but I don’t know how to fix it yet.
set.seed(494)
rf_pdp <- model_profile(explainer = rf_explain, variables = "annual_inc")
plot(rf_pdp,
variables = "annual_inc",
geom = "profiles")

rf_pdp2 <- model_profile(explainer = rf_explain, variables = "int_rate")
plot(rf_pdp2,
variables = "int_rate",
geom = "profiles")

rf_pdp3 <- model_profile(explainer = rf_explain, variables = "revol_util")
plot(rf_pdp3,
variables = "revol_util",
geom = "profiles")

- Fit one more model type of your choosing that will feed into the stacking model.
knn_mod <-
nearest_neighbor(
neighbors = tune("k")
) %>%
set_engine("kknn") %>%
set_mode("classification")
knn_wf <-
workflow() %>%
add_model(knn_mod) %>%
add_recipe(lending_rf_recipe)
knn_tune <-
knn_wf %>%
tune_grid(
lending_cv,
grid = 4,
control = ctrl_grid
)
- Create a model stack with the candidate models from the previous parts of the exercise and use the
blend_predictions() function to find the coefficients of the stacked model. Create a plot examining the performance metrics for the different penalty parameters to assure you have captured the best one. If not, adjust the penalty. (HINT: use the autoplot() function). Which models are contributing most?
The random forest model is contributing the most, with the knn model contributing a little and the lasso model not included in the stack model at all.
lending_stack <-
stacks() %>%
add_candidates(ranger_cv) %>%
add_candidates(lending_lasso_tune) %>%
add_candidates(knn_tune)
lending_blend <-
lending_stack %>%
blend_predictions()
lending_blend
## # A tibble: 2 x 3
## member type weight
## <chr> <chr> <dbl>
## 1 .pred_good_ranger_cv_1_1 rand_forest 13.0
## 2 .pred_good_knn_tune_1_1 nearest_neighbor 1.05
autoplot(lending_blend)

- Fit the final stacked model using
fit_members(). Apply the model to the test data and report the accuracy and area under the curve. Create a graph of the ROC and construct a confusion matrix. Comment on what you see. Save this final model using the saveRDS() function - see the Use the model section of the tidymodels intro. We are going to use the model in the next part. You’ll want to save it in the folder where you create your shiny app.
The stacked model looks incredibly accurate.
lending_final_stack <- lending_blend %>%
fit_members()
saveRDS(lending_final_stack, "final_mod.rds")
preds <-
lending_testing %>%
bind_cols(lending_final_stack %>%
predict(new_data = lending_testing)) %>%
bind_cols(lending_final_stack %>%
predict(new_data = lending_testing, type = "prob"))
preds
conf_mat(data = preds, estimate = .pred_class, truth = Class)
## Truth
## Prediction bad good
## bad 879 2
## good 0 2333
roc = roc(response = preds$Class, predictor = preds$.pred_good)
plot(roc)

LS0tDQp0aXRsZTogJ0Fzc2lnbm1lbnQgIzInDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkNCmBgYA0KDQpgYGB7ciBsaWJyYXJpZXN9DQojIFNFRSBtb2RlbGRhdGEgcGFja2FnZSBmb3IgbmV3IGRhdGFzZXRzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAjIGZvciBncmFwaGluZyBhbmQgZGF0YSBjbGVhbmluZw0KbGlicmFyeSh0aWR5bW9kZWxzKSAgICAgICAgIyBmb3IgbW9kZWxpbmcNCmxpYnJhcnkoc3RhY2tzKSAgICAgICAgICAgICMgZm9yIHN0YWNraW5nIG1vZGVscw0KbGlicmFyeShuYW5pYXIpICAgICAgICAgICAgIyBmb3IgZXhhbWluaW5nIG1pc3NpbmcgdmFsdWVzIChOQXMpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICAgICAgICAjIGZvciBkYXRlIG1hbmlwdWxhdGlvbg0KbGlicmFyeShtb2Rlcm5kaXZlKSAgICAgICAgIyBmb3IgS2luZyBDb3VudHkgaG91c2luZyBkYXRhDQpsaWJyYXJ5KHZpcCkgICAgICAgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzDQpsaWJyYXJ5KERBTEVYKSAgICAgICAgICAgICAjIGZvciBtb2RlbCBpbnRlcnByZXRhdGlvbiAgDQpsaWJyYXJ5KERBTEVYdHJhKSAgICAgICAgICAjIGZvciBleHRlbnNpb24gb2YgREFMRVgNCmxpYnJhcnkocGF0Y2h3b3JrKSAgICAgICAgICMgZm9yIGNvbWJpbmluZyBwbG90cyBuaWNlbHkNCmxpYnJhcnkocFJPQykNCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpICMgTGlzYSdzIGZhdm9yaXRlIHRoZW1lDQpgYGANCg0KYGBge3IgZGF0YX0NCmRhdGEoImxlbmRpbmdfY2x1YiIpDQojIERhdGEgZGljdGlvbmFyeSAoYXMgY2xvc2UgYXMgSSBjb3VsZCBmaW5kKTogaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS93b3Jkc2ZvcnRoZXdpc2UvbGVuZGluZy1jbHViL2Rpc2N1c3Npb24vMTcwNjkxDQpgYGANCg0KDQpXaGVuIHlvdSBmaW5pc2ggdGhlIGFzc2lnbm1lbnQsIHJlbW92ZSB0aGUgYCNgIGZyb20gdGhlIG9wdGlvbnMgY2h1bmsgYXQgdGhlIHRvcCwgc28gdGhhdCBtZXNzYWdlcyBhbmQgd2FybmluZ3MgYXJlbid0IHByaW50ZWQuIElmIHlvdSBhcmUgZ2V0dGluZyBlcnJvcnMgaW4geW91ciBjb2RlLCBhZGQgYGVycm9yID0gVFJVRWAgc28gdGhhdCB0aGUgZmlsZSBrbml0cy4gSSB3b3VsZCByZWNvbW1lbmQgbm90IHJlbW92aW5nIHRoZSBgI2AgdW50aWwgeW91IGFyZSBjb21wbGV0ZWx5IGZpbmlzaGVkLg0KDQojIyBQdXQgaXQgb24gR2l0SHViISAgICAgICAgDQoNCkZyb20gbm93IG9uLCBHaXRIdWIgc2hvdWxkIGJlIHBhcnQgb2YgeW91ciByb3V0aW5lIHdoZW4gZG9pbmcgYXNzaWdubWVudHMuIEkgcmVjb21tZW5kIG1ha2luZyBpdCBwYXJ0IG9mIHlvdXIgcHJvY2VzcyBhbnl0aW1lIHlvdSBhcmUgd29ya2luZyBpbiBSLCBidXQgSSdsbCBtYWtlIHlvdSBzaG93IGl0J3MgcGFydCBvZiB5b3VyIHByb2Nlc3MgZm9yIGFzc2lnbm1lbnRzLg0KDQoqKlRhc2sqKjogV2hlbiB5b3UgYXJlIGZpbmlzaGVkIHdpdGggdGhlIGFzc2lnbm1lbnQsIHBvc3QgYSBsaW5rIGJlbG93IHRvIHRoZSBHaXRIdWIgcmVwbyBmb3IgdGhlIGFzc2lnbm1lbnQuIElmIHlvdSB3YW50IHRvIHBvc3QgaXQgdG8geW91ciBwZXJzb25hbCB3ZWJzaXRlLCB0aGF0J3Mgb2sgKG5vdCByZXF1aXJlZCkuIE1ha2Ugc3VyZSB0aGUgbGluayBnb2VzIHRvIGEgc3BvdCBpbiB0aGUgcmVwbyB3aGVyZSBJIGNhbiBlYXNpbHkgZmluZCB0aGlzIGFzc2lnbm1lbnQuIEZvciBleGFtcGxlLCBpZiB5b3UgaGF2ZSBhIHdlYnNpdGUgd2l0aCBhIGJsb2cgYW5kIHBvc3QgdGhlIGFzc2lnbm1lbnQgYXMgYSBibG9nIHBvc3QsIGxpbmsgdG8gdGhlIHBvc3QncyBmb2xkZXIgaW4gdGhlIHJlcG8uIEFzIGFuIGV4YW1wbGUsIEkndmUgbGlua2VkIHRvIG15IEdpdEh1YiBzdGFja2luZyBtYXRlcmlhbCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2xsZW5kd2F5L2Fkc193ZWJzaXRlL3RyZWUvbWFzdGVyL19wb3N0cy8yMDIxLTAzLTIyLXN0YWNraW5nKS4NCg0KW0dpdEh1YiBSZXBvXShodHRwczovL2dpdGh1Yi5jb20vaGF5bGV5aGFkZ2VzL1NUQVQ0OTRBc3NpZ25tZW50MikNCg0KIyMgTW9kZWxpbmcNCg0KQmVmb3JlIGp1bXBpbmcgaW50byB0aGVzZSBwcm9ibGVtcywgeW91IHNob3VsZCByZWFkIHRocm91Z2ggKGFuZCBmb2xsb3cgYWxvbmcgd2l0aCEpIHRoZSBbbW9kZWwgc3RhY2tpbmddKGh0dHBzOi8vYWR2YW5jZWQtZHMtaW4tci5uZXRsaWZ5LmFwcC9wb3N0cy8yMDIxLTAzLTIyLXN0YWNraW5nLykgYW5kIFtnbG9iYWwgbW9kZWwgaW50ZXJwcmV0YXRpb25dKGh0dHBzOi8vYWR2YW5jZWQtZHMtaW4tci5uZXRsaWZ5LmFwcC9wb3N0cy8yMDIxLTAzLTI0LWltbGdsb2JhbC8pIHR1dG9yaWFscyBvbiB0aGUgQ291cnNlIE1hdGVyaWFscyB0YWIgb2YgdGhlIGNvdXJzZSB3ZWJzaXRlLg0KDQpXZSdsbCBiZSB1c2luZyB0aGUgYGxlbmRpbmdfY2x1YmAgZGF0YXNldCBmcm9tIHRoZSBgbW9kZWxkYXRhYCBsaWJyYXJ5LCB3aGljaCBpcyBwYXJ0IG9mIGB0aWR5bW9kZWxzYC4gVGhlIGRhdGEgZGljdGlvbmFyeSB0aGV5IHJlZmVyZW5jZSBkb2Vzbid0IHNlZW0gdG8gZXhpc3QgYW55bW9yZSwgYnV0IGl0IHNlZW1zIHRoZSBvbmUgb24gdGhpcyBba2FnZ2xlIGRpc2N1c3Npb25dKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vd29yZHNmb3J0aGV3aXNlL2xlbmRpbmctY2x1Yi9kaXNjdXNzaW9uLzE3MDY5MSkgaXMgcHJldHR5IGNsb3NlLiBJdCBtaWdodCBhbHNvIGhlbHAgdG8gcmVhZCBhIGJpdCBhYm91dCBbTGVuZGluZyBDbHViXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MZW5kaW5nQ2x1YikgYmVmb3JlIHN0YXJ0aW5nIGluIG9uIHRoZSBleGVyY2lzZXMuDQoNClRoZSBvdXRjb21lIHdlIGFyZSBpbnRlcmVzdGVkIGluIHByZWRpY3RpbmcgaXMgYENsYXNzYC4gQW5kIGFjY29yZGluZyB0byB0aGUgZGF0YXNldCdzIGhlbHAgcGFnZSwgaXRzIHZhbHVlcyBhcmUgImVpdGhlciAnZ29vZCcgKG1lYW5pbmcgdGhhdCB0aGUgbG9hbiB3YXMgZnVsbHkgcGFpZCBiYWNrIG9yIGN1cnJlbnRseSBvbi10aW1lKSBvciAnYmFkJyAoY2hhcmdlZCBvZmYsIGRlZmF1bHRlZCwgb2YgMjEtMTIwIGRheXMgbGF0ZSkiLg0KDQoqKlRhc2tzOioqIEkgd2lsbCBiZSBleHBhbmRpbmcgdGhlc2UsIGJ1dCB0aGlzIGdpdmVzIGEgZ29vZCBvdXRsaW5lLg0KDQoxLiBFeHBsb3JlIHRoZSBkYXRhLCBjb25jZW50cmF0aW5nIG9uIGV4YW1pbmluZyBkaXN0cmlidXRpb25zIG9mIHZhcmlhYmxlcyBhbmQgZXhhbWluaW5nIG1pc3NpbmcgdmFsdWVzLiANCg0KVGhlcmUgYXJlIG5vIE5BIHZhbHVlcyBpbiB0aGUgZGF0YS4NCg0KYGBge3J9DQpsZW5kaW5nX2NsdWIgJT4lIA0KIHNlbGVjdChldmVyeXRoaW5nKCkpICU+JSANCiAgc3VtbWFyaXNlX2FsbChmdW5zKHN1bShpcy5uYSguKSkpKQ0KYGBgDQoNCg0KSXQgbG9va3MgbGlrZSBhIG1ham9yaXR5IG9mIHRoZSBudW1lcmljIHZhcmlhYmxlcyBhcmUgcmlnaHQgc2tld2VkLiBJIGNhbiBzZWUgd2h5IExpc2EgZGVjaWRlZCB0byBhZGQgbW9yZSAnYmFkJyBjYXNlcyB0byB0aGUgZGF0YS4NCg0KYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0gMTB9DQpsZW5kaW5nX2NsdWIgJT4lDQogIGtlZXAoaXMubnVtZXJpYykgJT4lIA0KICBnYXRoZXIoKSAlPiUgDQogIGdncGxvdChhZXModmFsdWUpKSArDQogICAgZmFjZXRfd3JhcCh+IGtleSwgc2NhbGVzID0gImZyZWUiKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oKQ0KDQpsZW5kaW5nX2NsdWIgJT4lIA0KICBzZWxlY3Qod2hlcmUoaXMuZmFjdG9yKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwNCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKw0KICBnZW9tX2JhcigpICsNCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwgDQogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLCANCiAgICAgICAgICAgICBucm93ID0gMikgDQpgYGANCg0KMi4gRG8gYW55IGRhdGEgY2xlYW5pbmcgc3RlcHMgdGhhdCBuZWVkIHRvIGhhcHBlbiBiZWZvcmUgdGhlIG1vZGVsIGlzIGJ1aWxkLiBGb3IgZXhhbXBsZSwgeW91IG1pZ2h0IHJlbW92ZSBhbnkgdmFyaWFibGVzIHRoYXQgbWVhbiB0aGUgc2FtZSB0aGluZyBhcyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgKG5vdCBzdXJlIGlmIHRoYXQgaGFwcGVucyBoZXJlKSwgZ2V0IHJpZCBvZiByb3dzIHdoZXJlIGFsbCB2YXJpYWJsZXMgaGF2ZSBtaXNzaW5nIHZhbHVlcywgZXRjLiANCg0KYGBge3J9DQpsZW5kaW5nX2NsdWIgPC0NCmxlbmRpbmdfY2x1YiAlPiUgDQogIHNlbGVjdCgtZGVsaW5xX2FtbnQsIC1hY2Nfbm93X2RlbGlucSkNCmBgYA0KDQpCZSBzdXJlIHRvIGFkZCBtb3JlICJiYWQiIENsYXNzZXMuIFRoaXMgaXMgbm90IHRoZSBiZXN0IHNvbHV0aW9uLCBidXQgaXQgd2lsbCB3b3JrIGZvciBub3cuIChTaG91bGQgaW52ZXN0aWdhdGUgaG93IHRvIGFwcHJvcHJpYXRlbHkgdXNlIGBzdGVwX3NhbXBsZV91cCgpYCBmdW5jdGlvbiBmcm9tIFtgdGhlbWlzYF0oaHR0cHM6Ly9naXRodWIuY29tL3RpZHltb2RlbHMvdGhlbWlzKSkuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNDk0KQ0KY3JlYXRlX21vcmVfYmFkIDwtIGxlbmRpbmdfY2x1YiAlPiUgDQogIGZpbHRlcihDbGFzcyA9PSAiYmFkIikgJT4lIA0KICBzYW1wbGVfbihzaXplID0gMzAwMCwgcmVwbGFjZSA9IFRSVUUpDQoNCmxlbmRpbmdfY2x1Yl9tb2QgPC0gbGVuZGluZ19jbHViICU+JSANCiAgYmluZF9yb3dzKGNyZWF0ZV9tb3JlX2JhZCkNCmBgYA0KDQozLiBTcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0LCBwdXR0aW5nIDc1XCUgaW4gdGhlIHRyYWluaW5nIGRhdGEuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNDk0KSAjIGZvciByZXByb2R1Y2liaWxpdHkNCg0KbGVuZGluZ19zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGxlbmRpbmdfY2x1Yl9tb2QsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9wID0gLjc1LCBzdHJhdGEgPSBDbGFzcykNCg0KbGVuZGluZ190cmFpbmluZyA8LSB0cmFpbmluZyhsZW5kaW5nX3NwbGl0KQ0KbGVuZGluZ190ZXN0aW5nIDwtIHRlc3RpbmcobGVuZGluZ19zcGxpdCkNCmBgYA0KDQo0LiBTZXQgdXAgdGhlIHJlY2lwZSBhbmQgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzIHRvIGJ1aWxkIGEgbGFzc28gbW9kZWwuIFNvbWUgc3RlcHMgeW91IHNob3VsZCB0YWtlOg0KDQoqIE1ha2UgYWxsIGludGVnZXIgdmFyaWFibGVzIG51bWVyaWMgKEknZCBoaWdobHkgcmVjb21tZW5kIHVzaW5nIGBzdGVwX211dGF0ZV9hdCgpYCBvciB0aGlzIHdpbGwgYmUgYSBsb3Qgb2YgY29kZSkuIFdlJ2xsIHdhbnQgdG8gZG8gdGhpcyBmb3IgdGhlIG1vZGVsIGludGVycHJldGF0aW9uIHdlJ2xsIGRvIGxhdGVyLiAgDQoqIFRoaW5rIGFib3V0IGdyb3VwaW5nIGZhY3RvciB2YXJpYWJsZXMgd2l0aCBtYW55IGxldmVscy4gIA0KKiBNYWtlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBkdW1teSB2YXJpYWJsZXMgKG1ha2Ugc3VyZSBOT1QgdG8gZG8gdGhpcyB0byB0aGUgb3V0Y29tZSB2YXJpYWJsZSkuICANCiogTm9ybWFsaXplIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMuICANCg0KYGBge3J9DQpsZW5kaW5nX3JlY2lwZSA8LQ0KICByZWNpcGUoZm9ybXVsYSA9IENsYXNzIH4gLiwgDQogICAgICAgICBkYXRhID0gbGVuZGluZ190cmFpbmluZykgJT4lIA0KICBzdGVwX211dGF0ZV9hdChhbGxfbnVtZXJpYygpLCBmbiA9IH5hcy5udW1lcmljKC4pKSAlPiUgDQogIHN0ZXBfbXV0YXRlKHZlcmlmaWNhdGlvbl9zdGF0dXMgPSBhcy5mYWN0b3IoaWZlbHNlKHZlcmlmaWNhdGlvbl9zdGF0dXMgPT0gIk5vdF9WZXJpZmllZCIsICJOb3RfVmVyaWZpZWQiLCAiVmVyaWZpZWQiKSksDQogICAgICAgICAgICAgIHN1Yl9ncmFkZSA9IGZjdF9jb2xsYXBzZShzdWJfZ3JhZGUsDQogICAgICAgICAgICAgICAgICAgIEEgPSBjKCJBMSIsICJBMiIsICJBMyIsICJBNCIsICJBNSIpLA0KICAgICAgICAgICAgICAgICAgICBCID0gYygiQjEiLCAiQjIiLCAiQjMiLCAiQjQiLCAiQjUiKSwNCiAgICAgICAgICAgICAgICAgICAgQyA9IGMoIkMxIiwgIkMyIiwgIkMzIiwgIkM0IiwgIkM1IiksDQogICAgICAgICAgICAgICAgICAgIEQgPSBjKCJEMSIsICJEMiIsICJEMyIsICJENCIsICJENSIpLA0KICAgICAgICAgICAgICAgICAgICBFID0gYygiRTEiLCAiRTIiLCAiRTMiLCAiRTQiLCAiRTUiKSwNCiAgICAgICAgICAgICAgICAgICAgZiA9IGMoIkYxIiwgIkYyIiwgIkYzIiwgIkY0IiwgIkY1IiksDQogICAgICAgICAgICAgICAgICAgIEcgPSBjKCJHMSIsICJHMiIsICJHMyIsICJHNCIsICJHNSIpKSwNCiAgICAgICAgICAgICAgYWRkcl9zdGF0ZSA9IGZjdF9jb2xsYXBzZShhZGRyX3N0YXRlLA0KICAgICAgICAgICAgICAgICAgICBNaWR3ZXN0ID0gYygiTkQiLCAiU0QiLCAiTkUiLCAiS1MiLCAiTU4iLCAiV0kiLCAiSUwiLCAiSU4iLCAiTUkiLCAiT0giLCAiTU8iKSwNCiAgICAgICAgICAgICAgICAgICAgU291dGggPSBjKCJPSyIsICJUWCIsICJBUiIsICJMQSIsICJNUyIsICJBTCIsICJHQSIsICJGTCIsICJUTiIsICJLWSIsICJTQyIsICJOQyIsICJWQSIsICJXViIsICJNRCIsICJERSIsICJEQyIpLA0KICAgICAgICAgICAgICAgICAgICBOb3J0aGVhc3QgPSBjKCJOWSIsICJQQSIsICJOSiIsICJDVCIsICJWVCIsICJNQSIsICJOSCIsICJNRSIsICJSSSIpLA0KICAgICAgICAgICAgICAgICAgICBXZXN0ID0gYygiV0EiLCAiT1IiLCAiQ0EiLCAiTVQiLCAiSUQiLCAiV1kiLCAiTlYiLCAiVVQiLCAiQVoiLCAiQ08iLCAiTk0iKSwNCiAgICAgICAgICAgICAgICAgICAgUGFjaWZpYyA9IGMoIkhJIikpKSAlPiUgDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgDQogICAgICAgICAgICAgLWFsbF9vdXRjb21lcygpKSAlPiUgDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljKCkpDQoNCg0KbGVuZGluZ19yZWNpcGUgJT4lIA0KICBwcmVwKGxlbmRpbmdfdHJhaW5pbmcpICU+JQ0KICBqdWljZSgpIA0KYGBgDQoNCg0KNS4gU2V0IHVwIHRoZSBsYXNzbyBtb2RlbCBhbmQgd29ya2Zsb3cuIFdlIHdpbGwgdHVuZSB0aGUgYHBlbmFsdHlgIHBhcmFtZXRlci4NCg0KYGBge3J9DQpsZW5kaW5nX2xhc3NvX21vZCA8LSANCiAgbG9naXN0aWNfcmVnKG1peHR1cmUgPSAxKSAlPiUgDQogIHNldF9lbmdpbmUoImdsbW5ldCIpICU+JSANCiAgc2V0X2FyZ3MocGVuYWx0eSA9IHR1bmUoKSkgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KDQpsZW5kaW5nX2xhc3NvX3dmIDwtIA0KICB3b3JrZmxvdygpICU+JSANCiAgYWRkX3JlY2lwZShsZW5kaW5nX3JlY2lwZSkgJT4lIA0KICBhZGRfbW9kZWwobGVuZGluZ19sYXNzb19tb2QpDQoNCg0KcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IDEwKQ0KYGBgDQoNCjYuIFNldCB1cCB0aGUgbW9kZWwgdHVuaW5nIGZvciB0aGUgYHBlbmFsdHlgIHBhcmFtZXRlci4gQmUgc3VyZSB0byBhZGQgdGhlIGBjb250cm9sX3N0YWNrX2dyaWQoKWAgZm9yIHRoZSBgY29udHJvbGAgYXJndW1lbnQgc28gd2UgY2FuIHVzZSB0aGVzZSByZXN1bHRzIGxhdGVyIHdoZW4gd2Ugc3RhY2suIEZpbmQgdGhlIGFjY3VyYWN5IGFuZCBhcmVhIHVuZGVyIHRoZSByb2MgY3VydmUgZm9yIHRoZSBtb2RlbCB3aXRoIHRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXIuICBVc2UgNS1mb2xkIGN2Lg0KDQpQZW5hbHR5ID0gNC42NDE1ODllLTA0LCBhY2N1cmFjeSA9IDAuNzQ4MzEzNiwgcm9jX2F1YyA9IDAuNzU4OTc1OA0KDQpgYGB7cn0NCnNldC5zZWVkKDQ5NCkgI2ZvciByZXByb2R1Y2libGUgNS1mb2xkDQoNCmxlbmRpbmdfY3YgPC0gdmZvbGRfY3YobGVuZGluZ190cmFpbmluZywgdiA9IDUpDQoNCmN0cmxfZ3JpZCA8LSBjb250cm9sX3N0YWNrX2dyaWQoKQ0KDQptZXRyaWMgPC0gbWV0cmljX3NldChhY2N1cmFjeSkNCg0KDQpsZW5kaW5nX2xhc3NvX3R1bmUgPC0gDQogIGxlbmRpbmdfbGFzc29fd2YgJT4lIA0KICB0dW5lX2dyaWQoDQogICAgcmVzYW1wbGVzID0gbGVuZGluZ19jdiwNCiAgICBncmlkID0gcGVuYWx0eV9ncmlkLA0KICAgIGNvbnRyb2wgPSBjdHJsX2dyaWQNCiAgICApDQoNCmxlbmRpbmdfbGFzc29fdHVuZSU+JSANCiAgY29sbGVjdF9tZXRyaWNzKCkgDQpgYGANCg0KDQo3LiBTZXQgdXAgdGhlIHJlY2lwZSBhbmQgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzIHRvIGJ1aWxkIGEgcmFuZG9tIGZvcmVzdCBtb2RlbC4gWW91IHNob3VsZG4ndCBoYXZlIHRvIGRvIGFzIG1hbnkgc3RlcHMuIFRoZSBvbmx5IHN0ZXAgeW91IHNob3VsZCBuZWVkIHRvIGRvIGlzIG1ha2luZyBhbGwgaW50ZWdlcnMgbnVtZXJpYy4gDQoNCmBgYHtyfQ0KbGVuZGluZ19yZl9yZWNpcGUgPC0NCiAgcmVjaXBlKGZvcm11bGEgPSBDbGFzcyB+IC4sIA0KICAgICAgICAgZGF0YSA9IGxlbmRpbmdfdHJhaW5pbmcpICU+JSANCiAgc3RlcF9tdXRhdGVfYXQoYWxsX251bWVyaWMoKSwgZm4gPSB+YXMubnVtZXJpYyguKSkNCmBgYA0KDQo4LiBTZXQgdXAgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgYW5kIHdvcmtmbG93LiBXZSB3aWxsIHR1bmUgdGhlIGBtdHJ5YCBhbmQgYG1pbl9uYCBwYXJhbWV0ZXJzIGFuZCBzZXQgdGhlIG51bWJlciBvZiB0cmVlcywgYHRyZWVzYCwgdG8gMTAwIChvdGhlcndpc2UgdGhlIG5leHQgc3RlcHMgdGFrZSB0b28gbG9uZykuDQoNCmBgYHtyfQ0KbGVuZGluZ19yZl9zcGVjIDwtIA0KICByYW5kX2ZvcmVzdChtdHJ5ID0gdHVuZSgpLCANCiAgICAgICAgICAgICAgbWluX24gPSB0dW5lKCksIA0KICAgICAgICAgICAgICB0cmVlcyA9IDEwMCkgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUgDQogIHNldF9lbmdpbmUoInJhbmdlciIpDQoNCmxlbmRpbmdfcmZfd2YgPC0gDQogIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfcmVjaXBlKGxlbmRpbmdfcmZfcmVjaXBlKSAlPiUgDQogIGFkZF9tb2RlbChsZW5kaW5nX3JmX3NwZWMpIA0KYGBgDQoNCjkuIFNldCB1cCB0aGUgbW9kZWwgdHVuaW5nIGZvciBib3RoIHRoZSBgbXRyeWAgYW5kIGBtaW5fbmAgcGFyYW1ldGVycy4gQmUgc3VyZSB0byBhZGQgdGhlIGBjb250cm9sX3N0YWNrX2dyaWQoKWAgZm9yIHRoZSBgY29udHJvbGAgYXJndW1lbnQgc28gd2UgY2FuIHVzZSB0aGVzZSByZXN1bHRzIGxhdGVyIHdoZW4gd2Ugc3RhY2suIFVzZSBvbmx5IDMgbGV2ZWxzIGluIHRoZSBncmlkLiBGb3IgdGhlIGBtdHJ5YCBwYXJhbWV0ZXIsIHlvdSBuZWVkIHRvIHB1dCBgZmluYWxpemUobXRyeSgpLCBsZW5kaW5nX3RyYWluaW5nICU+JSBzZWxlY3QoLUNsYXNzKSlgIGluIGFzIGFuIGFyZ3VtZW50IGluc3RlYWQgb2YganVzdCBgbXRyeSgpYCwgd2hlcmUgYGxlbmRpbmdfdHJhaW5pbmdgIGlzIHRoZSBuYW1lIG9mIHlvdXIgdHJhaW5pbmcgZGF0YS4gVGhpcyBpcyBiZWNhdXNlIHRoZSBgbXRyeSgpYCBncmlkIHdpbGwgb3RoZXJ3aXNlIGhhdmUgdW5rbm93bnMgaW4gaXQuIFRoaXMgcGFydCBjYW4gdGFrZSBhIHdoaWxlIHRvIHJ1bi4NCg0KYGBge3J9DQptdHJ5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKGZpbmFsaXplKG10cnkoKSwgbGVuZGluZ190cmFpbmluZyAlPiUgc2VsZWN0KC1DbGFzcykpLCBtaW5fbigpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAzKQ0KDQpsZW5kaW5nX3JmX3R1bmUgPC0gDQogIGxlbmRpbmdfcmZfd2YgJT4lIA0KICB0dW5lX2dyaWQoDQogICAgcmVzYW1wbGVzID0gbGVuZGluZ19jdiwNCiAgICBncmlkID0gbXRyeV9ncmlkLA0KICAgIGNvbnRyb2wgPSBjdHJsX2dyaWQNCiAgICApDQpgYGANCg0KMTAuIEZpbmQgdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlcnMuIFdoYXQgaXMgdGhlIGFyZSB0aGUgYWNjdXJhY3kgYW5kIGFyZWEgdW5kZXIgdGhlIFJPQyBjdXJ2ZSBmb3IgdGhlIG1vZGVsIHdpdGggdGhvc2UgdHVuaW5nIHBhcmFtZXRlcnM/DQoNClRoZSBtZXRyaWMgd2l0aCB0aGUgaGlnaGVzdCBhY2N1cmFjeSBpcyBtdHJ5ID0gMTAgYW5kIG1pbl9uID0gMiwgd2l0aCBhbiBhY2N1cmFjeSBvZiAwLjk5Mjg0NTIgYW5kIGFuIGFyZWEgdW5kZXIgdGhlIFJPQyBjdXJ2ZSBvZiAwLjk5NzIwODkuCQ0KYGBge3J9DQpsZW5kaW5nX3JmX3R1bmUlPiUgDQogIGNvbGxlY3RfbWV0cmljcygpIA0KYGBgDQoNCkVYVFJBOiBTZXQgdXAgdGhlIGZpbmFsaXplZCByYW5kb20gZm9yZXN0IG1vZGVsIGFuZCB3b3JrZmxvdywgdGhlbiBmaXQgdGhlIGZpbmFsaXplZCBtb2RlbC4gKGZvciBxdWVzdGlvbiAxMSkNCg0KYGBge3J9DQpmaW5hbF9yZl9zcGVjIDwtIA0KICByYW5kX2ZvcmVzdChtdHJ5ID0gMTAsIA0KICAgICAgICAgICAgICBtaW5fbiA9IDIsIA0KICAgICAgICAgICAgICB0cmVlcyA9IDEwMCkgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUgDQogIHNldF9lbmdpbmUoInJhbmdlciIpDQoNCmZpbmFsX3JmX3dmIDwtIA0KICB3b3JrZmxvdygpICU+JSANCiAgYWRkX3JlY2lwZShsZW5kaW5nX3JmX3JlY2lwZSkgJT4lIA0KICBhZGRfbW9kZWwoZmluYWxfcmZfc3BlYykgDQpgYGANCg0KYGBge3J9DQpyZl9maXQgPC0gZmluYWxfcmZfd2YgJT4lIA0KICBmaXQobGVuZGluZ190cmFpbmluZykNCmBgYA0KDQpFWFRSQTogDQoNCmBgYHtyfQ0KY3RybF9yZXMgPC0gY29udHJvbF9zdGFja19yZXNhbXBsZXMoKQ0KDQpyYW5nZXJfY3YgPC0gZmluYWxfcmZfd2YgJT4lIA0KICBmaXRfcmVzYW1wbGVzKGxlbmRpbmdfY3YsIA0KICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBjdHJsX3JlcykNCg0KY29sbGVjdF9tZXRyaWNzKHJhbmdlcl9jdikNCmBgYA0KDQoxMS4gVXNlIGZ1bmN0aW9ucyBmcm9tIHRoZSBgREFMRVhgIGFuZCBgREFMRVh0cmFgIGxpYnJhcmllcyB0byBjcmVhdGUgYSBoaXN0b2dyYW0gYW5kIGJveHBsb3Qgb2YgdGhlIHJlc2lkdWFscyBmcm9tIHRoZSB0cmFpbmluZyBkYXRhLiBIb3cgZG8gdGhleSBsb29rPyBBbnkgaW50ZXJlc3RpbmcgYmVoYXZpb3I/DQoNClRoZSByZXNpZHVhbHMgYXJlIHNrZXdlZCB0byB0aGUgcmlnaHQsIGFsdGhvdWdoIGl0cyBtb2RlIGlzIGF0IDAgdGhlIG1lYW4gcmVzaWR1YWwgd291bGQgYmUgZ3JlYXRlciB0aGFuIDAgKHdoaWNoIGNhbiBiZSBzZWVuIGluIHRoZSBib3hwbG90cykuIA0KDQpgYGB7cn0NCnJmX2V4cGxhaW4gPC0gDQogIGV4cGxhaW5fdGlkeW1vZGVscygNCiAgICBtb2RlbCA9IHJmX2ZpdCwNCiAgICBkYXRhID0gbGVuZGluZ190cmFpbmluZyAlPiUgc2VsZWN0KC1DbGFzcyksIA0KICAgIHkgPWxlbmRpbmdfdHJhaW5pbmcgJT4lIA0KICAgICAgbXV0YXRlKENsYXNzX251bSA9IGFzLmludGVnZXIoQ2xhc3MgPT0iZ29vZCIpKSAlPiUgDQogICAgICBwdWxsKENsYXNzX251bSksDQogICAgbGFiZWwgPSAicmYiDQogICkNCg0KcmZfbW9kX3BlcmYgPC0gIG1vZGVsX3BlcmZvcm1hbmNlKHJmX2V4cGxhaW4pDQoNCmhpc3RfcGxvdCA8LSANCiAgcGxvdChyZl9tb2RfcGVyZiwNCiAgICAgICByZl9tb2RfcGVyZiwgDQogICAgICAgZ2VvbSA9ICJoaXN0b2dyYW0iKQ0KYm94X3Bsb3QgPC0NCiAgcGxvdChyZl9tb2RfcGVyZiwNCiAgICAgICByZl9tb2RfcGVyZiwgDQogICAgICAgZ2VvbSA9ICJib3hwbG90IikNCg0KaGlzdF9wbG90DQpib3hfcGxvdA0KYGBgDQoNCjEyLiBVc2UgYERBTEVYYCBmdW5jdGlvbnMgdG8gY3JlYXRlIGEgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90IGZyb20gdGhpcyBtb2RlbC4gV2hhdCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcz8NCg0KVGhlIGludF9yYXRlLCBhbm51YWtfaW5jLCBhbGxfdXRpbCwgc3ViX2dyYWRlLCByZXZvbF91dGlsLCBhbmQgZW1wX2xlbmd0aCBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyBpbiB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbC4NCg0KYGBge3J9DQpzZXQuc2VlZCg0OTQpDQpyZl92YXJfaW1wIDwtIA0KICBtb2RlbF9wYXJ0cygNCiAgICByZl9leHBsYWluDQogICAgKQ0KDQpwbG90KHJmX3Zhcl9pbXAsIHNob3dfYm94cGxvdHMgPSBUUlVFKQ0KYGBgDQoNCjEzLiBXcml0ZSBhIGZ1bmN0aW9uIGNhbGxlZCBgY3BfcHJvZmlsZWAgdG8gbWFrZSBhIENQIHByb2ZpbGUuIFRoZSBmdW5jdGlvbiB3aWxsIHRha2UgYW4gZXhwbGFpbmVyLCBhIG5ldyBvYnNlcnZhdGlvbiwgYW5kIGEgdmFyaWFibGUgbmFtZSBhcyBpdHMgYXJndW1lbnRzIGFuZCBjcmVhdGUgYSBDUCBwcm9maWxlIGZvciBhIHF1YW50aXRhdGl2ZSBwcmVkaWN0b3IgdmFyaWFibGUuIFlvdSB3aWxsIG5lZWQgdG8gdXNlIHRoZSBgcHJlZGljdF9wcm9maWxlKClgIGZ1bmN0aW9uIGluc2lkZSB0aGUgZnVuY3Rpb24geW91IGNyZWF0ZSAtIHB1dCB0aGUgdmFyaWFibGUgbmFtZSB0aGVyZSBzbyB0aGUgcGxvdHRpbmcgcGFydCBpcyBlYXNpZXIuIFlvdSdsbCBhbHNvIHdhbnQgdG8gdXNlIGBhZXNfc3RyaW5nKClgIHJhdGhlciB0aGFuIGBhZXMoKWAgYW5kIHF1b3RlIHRoZSB2YXJpYWJsZXMuIFVzZSB0aGUgYGNwX3Byb2ZpbGUoKWAgZnVuY3Rpb24gdG8gY3JlYXRlIG9uZSBDUCBwcm9maWxlIG9mIHlvdXIgY2hvb3NpbmcuIEJlIHN1cmUgdG8gY2hvb3NlIGEgdmFyaWFibGUgdGhhdCBpcyBudW1lcmljLCBub3QgaW50ZWdlci4gVGhlcmUgc2VlbSB0byBiZSBpc3N1ZXMgd2l0aCB0aG9zZSB0aGF0IEknbSBsb29raW5nIGludG8uDQoNCmBgYHtyfQ0KY3BfcHJvZmlsZSA8LSBmdW5jdGlvbihleHBsYWluZXIsIG5ld19vYnMsIHZhcikgew0KICBjcCA8LQ0KICAgIHByZWRpY3RfcHJvZmlsZShleHBsYWluZXIgPSBleHBsYWluZXIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzLCB2YXJpYWJsZXMgPSB2YXIpDQogIGNwICU+JSANCiAgcmVuYW1lKHloYXQgPSBgX3loYXRfYCkgJT4lIA0KICBnZ3Bsb3QoYWVzX3N0cmluZyh4ID0gdmFyLA0KICAgICAgICAgICAgIHkgPSAieWhhdCIpKSArDQogIGdlb21fbGluZSgpIA0KfQ0KDQpvYiA8LSANCiAgbGVuZGluZ190ZXN0aW5nICU+JSANCiAgc2xpY2UoNCkNCg0KY3BfcHJvZmlsZShyZl9leHBsYWluLCBvYiwgImludF9yYXRlIikNCmBgYA0KDQpGb3IgYW4gZXh0cmEgY2hhbGxlbmdlLCB3cml0ZSBhIGZ1bmN0aW9uIHRoYXQgd2lsbCB3b3JrIGZvciBlaXRoZXIgYSBxdWFudGl0YXRpdmUgb3IgY2F0ZWdvcmljYWwgdmFyaWFibGUuIA0KDQpJZiB5b3UgbmVlZCBoZWxwIHdpdGggZnVuY3Rpb24gd3JpdGluZyBjaGVjayBvdXQgdGhlIFtGdW5jdGlvbnNdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovZnVuY3Rpb25zLmh0bWwpIGNoYXB0ZXIgb2YgUjREUyBieSBXaWNraGFtIGFuZCBHcm9sZW11bmQuDQoNCg0KMTQuIFVzZSBgREFMRVhgIGZ1bmN0aW9ucyB0byBjcmVhdGUgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzICh3aXRoIHRoZSBDUCBwcm9maWxlcyBpbiBncmF5KSBmb3IgdGhlIDMtNCBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMuIElmIHRoZSBpbXBvcnRhbnQgdmFyaWFibGVzIGFyZSBjYXRlZ29yaWNhbCwgeW91IGNhbiBpbnN0ZWFkIG1ha2UgYSBDUCBwcm9maWxlIGZvciAzIG9ic2VydmF0aW9ucyBpbiB0aGUgZGF0YXNldCBhbmQgZGlzY3VzcyBob3cgeW91IGNvdWxkIGdvIGFib3V0IGNvbnN0cnVjdGluZyBhIHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90IGZvciBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlICh5b3UgZG9uJ3QgaGF2ZSB0byBjb2RlIGl0LCBidXQgeW91IGNhbiBpZiB5b3Ugd2FudCBhbiBleHRyYSBjaGFsbGVuZ2UpLiBJZiBpdCBldmVyIGdpdmVzIHlvdSBhbiBlcnJvciB0aGF0IHNheXMsICJFcnJvcjogQ2FuJ3QgY29udmVydCBmcm9tIGBWQVJJQUJMRWAgPGRvdWJsZT4gdG8gYFZBUklBQkxFYCA8aW50ZWdlcj4gZHVlIHRvIGxvc3Mgb2YgcHJlY2lzaW9uIiwgdGhlbiByZW1vdmUgdGhhdCB2YXJpYWJsZSBmcm9tIHRoZSBsaXN0LiBJIHNlZW0gdG8gaGF2ZSBmaWd1cmVkIG91dCB3aHkgaXQncyBkb2luZyB0aGF0LCBidXQgSSBkb24ndCBrbm93IGhvdyB0byBmaXggaXQgeWV0Lg0KDQpgYGB7cn0NCnNldC5zZWVkKDQ5NCkNCnJmX3BkcCA8LSBtb2RlbF9wcm9maWxlKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sIHZhcmlhYmxlcyA9ICJhbm51YWxfaW5jIikNCg0KcGxvdChyZl9wZHAsIA0KICAgICB2YXJpYWJsZXMgPSAiYW5udWFsX2luYyIsDQogICAgIGdlb20gPSAicHJvZmlsZXMiKQ0KDQoNCnJmX3BkcDIgPC0gbW9kZWxfcHJvZmlsZShleHBsYWluZXIgPSByZl9leHBsYWluLCB2YXJpYWJsZXMgPSAiaW50X3JhdGUiKQ0KDQpwbG90KHJmX3BkcDIsIA0KICAgICB2YXJpYWJsZXMgPSAiaW50X3JhdGUiLA0KICAgICBnZW9tID0gInByb2ZpbGVzIikNCg0KcmZfcGRwMyA8LSBtb2RlbF9wcm9maWxlKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sIHZhcmlhYmxlcyA9ICJyZXZvbF91dGlsIikNCg0KcGxvdChyZl9wZHAzLCANCiAgICAgdmFyaWFibGVzID0gInJldm9sX3V0aWwiLA0KICAgICBnZW9tID0gInByb2ZpbGVzIikNCmBgYA0KDQoxNS4gRml0IG9uZSBtb3JlIG1vZGVsIHR5cGUgb2YgeW91ciBjaG9vc2luZyB0aGF0IHdpbGwgZmVlZCBpbnRvIHRoZSBzdGFja2luZyBtb2RlbC4gDQoNCmBgYHtyfQ0Ka25uX21vZCA8LQ0KICBuZWFyZXN0X25laWdoYm9yKA0KICAgIG5laWdoYm9ycyA9IHR1bmUoImsiKQ0KICApICU+JQ0KICBzZXRfZW5naW5lKCJra25uIikgJT4lIA0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQ0KDQprbm5fd2YgPC0gDQogIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfbW9kZWwoa25uX21vZCkgJT4lDQogIGFkZF9yZWNpcGUobGVuZGluZ19yZl9yZWNpcGUpDQoNCmtubl90dW5lIDwtIA0KICBrbm5fd2YgJT4lIA0KICB0dW5lX2dyaWQoDQogICAgbGVuZGluZ19jdiwNCiAgICBncmlkID0gNCwNCiAgICBjb250cm9sID0gY3RybF9ncmlkDQogICkNCmBgYA0KDQoxNi4gQ3JlYXRlIGEgbW9kZWwgc3RhY2sgd2l0aCB0aGUgY2FuZGlkYXRlIG1vZGVscyBmcm9tIHRoZSBwcmV2aW91cyBwYXJ0cyBvZiB0aGUgZXhlcmNpc2UgYW5kIHVzZSB0aGUgYGJsZW5kX3ByZWRpY3Rpb25zKClgIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIGNvZWZmaWNpZW50cyBvZiB0aGUgc3RhY2tlZCBtb2RlbC4gQ3JlYXRlIGEgcGxvdCBleGFtaW5pbmcgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgZm9yIHRoZSBkaWZmZXJlbnQgcGVuYWx0eSBwYXJhbWV0ZXJzIHRvIGFzc3VyZSB5b3UgaGF2ZSBjYXB0dXJlZCB0aGUgYmVzdCBvbmUuIElmIG5vdCwgYWRqdXN0IHRoZSBwZW5hbHR5LiAoSElOVDogdXNlIHRoZSBgYXV0b3Bsb3QoKWAgZnVuY3Rpb24pLiBXaGljaCBtb2RlbHMgYXJlIGNvbnRyaWJ1dGluZyBtb3N0Pw0KDQpUaGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBpcyBjb250cmlidXRpbmcgdGhlIG1vc3QsIHdpdGggdGhlIGtubiBtb2RlbCBjb250cmlidXRpbmcgYSBsaXR0bGUgYW5kIHRoZSBsYXNzbyBtb2RlbCBub3QgaW5jbHVkZWQgaW4gdGhlIHN0YWNrIG1vZGVsIGF0IGFsbC4NCg0KYGBge3J9DQpsZW5kaW5nX3N0YWNrIDwtIA0KICBzdGFja3MoKSAlPiUgDQogIGFkZF9jYW5kaWRhdGVzKHJhbmdlcl9jdikgJT4lIA0KICBhZGRfY2FuZGlkYXRlcyhsZW5kaW5nX2xhc3NvX3R1bmUpICU+JSANCiAgYWRkX2NhbmRpZGF0ZXMoa25uX3R1bmUpDQpgYGANCg0KYGBge3J9DQpsZW5kaW5nX2JsZW5kIDwtIA0KICBsZW5kaW5nX3N0YWNrICU+JSANCiAgYmxlbmRfcHJlZGljdGlvbnMoKQ0KDQpsZW5kaW5nX2JsZW5kDQoNCmF1dG9wbG90KGxlbmRpbmdfYmxlbmQpDQpgYGANCg0KMTcuIEZpdCB0aGUgZmluYWwgc3RhY2tlZCBtb2RlbCB1c2luZyBgZml0X21lbWJlcnMoKWAuIEFwcGx5IHRoZSBtb2RlbCB0byB0aGUgdGVzdCBkYXRhIGFuZCByZXBvcnQgdGhlIGFjY3VyYWN5IGFuZCBhcmVhIHVuZGVyIHRoZSBjdXJ2ZS4gQ3JlYXRlIGEgZ3JhcGggb2YgdGhlIFJPQyBhbmQgY29uc3RydWN0IGEgY29uZnVzaW9uIG1hdHJpeC4gQ29tbWVudCBvbiB3aGF0IHlvdSBzZWUuIFNhdmUgdGhpcyBmaW5hbCBtb2RlbCB1c2luZyB0aGUgYHNhdmVSRFMoKWAgZnVuY3Rpb24gLSBzZWUgdGhlIFtVc2UgdGhlIG1vZGVsXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0xNi1tbC1yZXZpZXcvI3VzZS10aGUtbW9kZWwpIHNlY3Rpb24gb2YgdGhlIGB0aWR5bW9kZWxzYCBpbnRyby4gV2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgbW9kZWwgaW4gdGhlIG5leHQgcGFydC4gWW91J2xsIHdhbnQgdG8gc2F2ZSBpdCBpbiB0aGUgZm9sZGVyIHdoZXJlIHlvdSBjcmVhdGUgeW91ciBzaGlueSBhcHAuDQoNClRoZSBzdGFja2VkIG1vZGVsIGxvb2tzIGluY3JlZGlibHkgYWNjdXJhdGUuDQoNCmBgYHtyfQ0KbGVuZGluZ19maW5hbF9zdGFjayA8LSBsZW5kaW5nX2JsZW5kICU+JSANCiAgZml0X21lbWJlcnMoKQ0KDQpzYXZlUkRTKGxlbmRpbmdfZmluYWxfc3RhY2ssICJmaW5hbF9tb2QucmRzIikNCmBgYA0KDQpgYGB7cn0NCnByZWRzIDwtDQpsZW5kaW5nX3Rlc3RpbmcgJT4lIA0KICBiaW5kX2NvbHMobGVuZGluZ19maW5hbF9zdGFjayAlPiUgDQogIHByZWRpY3QobmV3X2RhdGEgPSBsZW5kaW5nX3Rlc3RpbmcpKSAlPiUgDQogIGJpbmRfY29scyhsZW5kaW5nX2ZpbmFsX3N0YWNrICU+JSANCiAgcHJlZGljdChuZXdfZGF0YSA9IGxlbmRpbmdfdGVzdGluZywgdHlwZSA9ICJwcm9iIikpDQoNCnByZWRzDQpgYGANCg0KYGBge3J9ICAgDQpjb25mX21hdChkYXRhID0gcHJlZHMsIGVzdGltYXRlID0gLnByZWRfY2xhc3MsIHRydXRoID0gQ2xhc3MpDQoNCnJvYyA9IHJvYyhyZXNwb25zZSA9IHByZWRzJENsYXNzLCBwcmVkaWN0b3IgPSBwcmVkcyQucHJlZF9nb29kKQ0KcGxvdChyb2MpDQpgYGANCg0KDQojIyBTaGlueSBhcHANCg0KSWYgeW91IGFyZSBuZXcgdG8gU2hpbnkgYXBwcyBvciBpdCdzIGJlZW4gYXdoaWxlIHNpbmNlIHlvdSd2ZSBtYWRlIG9uZSwgdmlzaXQgdGhlIFNoaW55IGxpbmtzIG9uIG91ciBjb3Vyc2UgW1Jlc291cmNlXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcmVzb3VyY2VzLmh0bWwpIHBhZ2UuIEkgd291bGQgcmVjb21tZW5kIHN0YXJ0aW5nIHdpdGggbXkgcmVzb3VyY2UgYmVjYXVzZSBpdCB3aWxsIGJlIHRoZSBtb3N0IGJhc2ljLiBZb3Ugd29uJ3QgYmUgZG9pbmcgYW55dGhpbmcgc3VwZXIgZmFuY3kgaW4gdGhpcyBhcHAuIA0KDQpFdmVyeW9uZSBzaG91bGQgd2F0Y2ggdGhlIFtUaGVtaW5nIFNoaW55XShodHRwczovL3lvdXR1LmJlL2I5V1dOTzRQMm5ZKSB0YWxrIGJ5IENhcnNvbiBTaWV2ZXJ0IHNvIHlvdSBjYW4gbWFrZSB5b3VyIGFwcCBsb29rIGFtYXppbmcuDQoNCioqVGFza3M6KioNCg0KWW91IGFyZSBnb2luZyB0byBjcmVhdGUgYW4gYXBwIHRoYXQgYWxsb3dzIGEgdXNlciB0byBleHBsb3JlIGhvdyB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGEgbG9hbiBiZWluZyBwYWlkIGJhY2sgKG9yIG1heWJlIGp1c3QgdGhlIHByZWRpY3RlZCBjbGFzcyAtIGVpdGhlciAiZ29vZCIgb3IgImJhZCIpIGNoYW5nZXMgZGVwZW5kaW5nIG9uIHRoZSB2YWx1ZXMgb2YgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMuDQoNClNwZWNpZmljYWxseSwgeW91IHdpbGwgZG8gdGhlIGZvbGxvd2luZzoNCg0KKiBTZXQgdXAgYSBzZXBhcmF0ZSBwcm9qZWN0IGFuZCBHaXRIdWIgcmVwbyBmb3IgdGhpcyBhcHAuIE1ha2Ugc3VyZSB0aGUgc2F2ZWQgbW9kZWwgZnJvbSB0aGUgcHJldmlvdXMgcHJvYmxlbSBpcyBhbHNvIGluIHRoYXQgZm9sZGVyLiBUaGUgYXBwIG5lZWRzIHRvIGJlIGNyZWF0ZWQgaW4gYSBmaWxlIGNhbGxlZCAqZXhhY3RseSogYXBwLlIgdGhhdCBpcyBhbHNvIGluIHRoZSBwcm9qZWN0IGZvbGRlci4gICANCiogQXQgdGhlIHRvcCBvZiB0aGUgZmlsZSwgbG9hZCBhbnkgbGlicmFyaWVzIHlvdSB1c2UgaW4gdGhlIGFwcC4gIA0KKiBVc2UgdGhlIGByZWFkUkRTKClgIGZ1bmN0aW9uIHRvIGxvYWQgdGhlIG1vZGVsLiAgDQoqIFlvdSBtYXkgd2FudCB0byBsb2FkIHNvbWUgb2YgdGhlIGRhdGEgdG8gdXNlDQoqIENyZWF0ZSBhIHVzZXIgaW50ZXJmYWNlICh1c2luZyB0aGUgdmFyaW91cyBgKklucHV0KClgIGZ1bmN0aW9ucykgd2hlcmUgc29tZW9uZSBjb3VsZCBlbnRlciB2YWx1ZXMgZm9yIGVhY2ggdmFyaWFibGUgdGhhdCBmZWVkcyBpbnRvIHRoZSBtb2RlbC4gWW91IHdpbGwgd2FudCB0byB0aGluayBoYXJkIGFib3V0IHdoaWNoIHR5cGVzIG9mIGAqSW5wdXQoKWAgZnVuY3Rpb25zIHRvIHVzZS4gVGhpbmsgYWJvdXQgaG93IHlvdSBjYW4gYmVzdCBwcmV2ZW50IG1pc3Rha2VzIChlZy4gZW50ZXJpbmcgZnJlZSB0ZXh0IGNvdWxkIGxlYWQgdG8gbWFueSBtaXN0YWtlcykuIA0KKiBBbm90aGVyIHBhcnQgb2YgdGhlIHVzZXIgaW50ZXJmYWNlIHdpbGwgYWxsb3cgdGhlbSB0byBjaG9vc2UgYSB2YXJpYWJsZSAoeW91IGNhbiBsaW1pdCB0aGlzIHRvIG9ubHkgdGhlIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMpIHdoZXJlIHRoZXkgY2FuIGV4cGxvcmUgdGhlIGVmZmVjdHMgb2YgY2hhbmdpbmcgdGhhdCB2YXJpYWJsZSwgaG9sZGluZyBhbGwgb3RoZXJzIGNvbnN0YW50LiAgDQoqIEFmdGVyIHRoZSB1c2VyIGhhcyBlbnRlcmVkIGFsbCB0aGUgcmVxdWlyZWQgdmFsdWVzLCB0aGUgb3V0cHV0IHdpbGwgYmUgYSBDUCBwcm9maWxlIHdpdGggdGhlIHRoZSBwcmVkaWN0ZWQgdmFsdWUgZm9yIHRoZSBkYXRhIHRoYXQgd2FzIGVudGVyZWQsIGluZGljYXRlZCBieSBhIHBvaW50LiBJIGRvbid0IHRoaW5rIHRoZSBmdW5jdGlvbnMgZnJvbSBgREFMRVhgIGFuZCBgREFMRVh0cmFgIHdpbGwgd29yayB3aXRoIGEgc3RhY2tlZCBtb2RlbCwgc28geW91J2xsIGxpa2VseSBoYXZlIHRvIChnZXQgdG8pIGRvIHNvbWUgb2YgeW91ciBvd24gY29kaW5nLiANCiogVXNlIHRoZSBgYnNsaWJgIHRvIHRoZW1lIHlvdXIgc2hpbnkgYXBwISAgDQoqIFB1Ymxpc2ggeW91ciBhcHAgdG8gW3NoaW55YXBwcy5pb10oaHR0cHM6Ly93d3cuc2hpbnlhcHBzLmlvLykuIFRoZXJlIGFyZSBpbnN0cnVjdGlvbnMgZm9yIGRvaW5nIHRoYXQgb24gdGhlIHR1dG9yaWFsIEkgbGlua2VkIHRvIGFib3ZlLiAgIA0KKiBXcml0ZSBhIHBhcmFncmFwaCBvciB0d28gZGVzY3JpYmluZyB5b3VyIGFwcCBvbiB5b3VyIHdlYnNpdGUhIExpbmsgdG8gdGhlIGFwcCBhbmQgeW91ciBHaXRIdWIgcmVwb3NpdG9yeSBpbiB5b3VyIHBvc3QuIEluY2x1ZGUgYSBsaW5rIHRvIHlvdXIgcG9zdCBoZXJlLiANCg0KSSBkaWRuJ3Qgd2FudCB0byBwdXQgdGhlIGFwcCBvbiBteSB3ZWJzaXRlIHNpbmNlIGl0J3Mgbm90IGFzIGNvbXBsZXRlIGFzIEknZCBsaWtlIGl0IHRvIGJlIGFuZCBJIGNvdWxkbid0IGZpZ3VyZSBvdXQgaG93IHRvIGZpeCB0aGUgZGVwbG95bWVudCBlcnJvci4NCg0KW1NoaW55IEFwcF0oaHR0cHM6Ly9oYXlsZXloYWRnZXMuc2hpbnlhcHBzLmlvL1NUQVQ0OTRBc3NpZ25tZW50MkFwcC8pDQoNCg0KIyMgQ29kZWQgQmlhcw0KDQpXYXRjaCB0aGUgW0NvZGUgQmlhc10oaHR0cHM6Ly93d3cucGJzLm9yZy9pbmRlcGVuZGVudGxlbnMvZmlsbXMvY29kZWQtYmlhcy8pIGZpbG0gYW5kIHdyaXRlIGEgc2hvcnQgcmVmbGVjdGlvbi4gSWYgeW91IHdhbnQgc29tZSBwcm9tcHRzLCByZWZsZWN0IG9uOiBXaGF0IHBhcnQgb2YgdGhlIGZpbG0gaW1wYWN0ZWQgeW91IHRoZSBtb3N0PyBXYXMgdGhlcmUgYSBwYXJ0IHRoYXQgc3VycHJpc2VkIHlvdSBhbmQgd2h5PyBXaGF0IGVtb3Rpb25zIGRpZCB5b3UgZXhwZXJpZW5jZSB3aGlsZSB3YXRjaGluZz8NCg0KDQpUaGVzZSBraW5kcyBvZiBtb3ZpZXMgYW5kIGRvY3VtZW50YXJpZXMgd29yayB0byBzaG9jayBhbmQgc2NhcmUgdGhlIHB1YmxpYyBpbnRvIGFjdGlvbiwgYW5kIHRvIHF1ZXN0aW9uIHRoZSBzdGF0dXMgcXVvLiBUaGlzIG1vdmllIGRpZCBhIGdvb2Qgam9iIG9mIHRoaXMgYnkgc2hvd2luZyBwb3NzaWJsZSBibGVhayBmdXR1cmVzIHRocm91Z2ggd2hhdCBpcyBnb2luZyBvbiBpbiBvdGhlciBwYXJ0cyBpbiB0aGUgd29ybGQgbGlrZSBDaGluYS4gSeKAmXZlIHNlZW4gYSB2aWRlbyBvciB0d28gYWJvdXQgdGhlIHN1cnZlaWxsYW5jZSBhbmQgYWR2YW5jZWQgQUkgdGVjaG5vbG9neSBpbiB1c2UgaW4gQ2hpbmEsIGJ1dCBoZWFyaW5nIGFib3V0IGl0IG5ldmVyIGZhaWxzIHRvIHNob2NrIG1lIGJlY2F1c2UgaXQgc291bmRzIGxpa2Ugc29tZXRoaW5nIHRoYXQgd291bGQgYmUgaW4gYSBzY2llbmNlIGZpY3Rpb24gbm92ZWwuIFRoZSBjcmVkaXQgc3lzdGVtIGluc3RpdHV0ZWQgdGhhdCB3b3JrcyB0byBjb250cm9sIGJlaGF2aW9yIGFuZCBzcGVlY2ggdGhlcmUgaXMgbWluZCBib2dnbGluZywgYnV0IHdoYXQgaXMgd29yc2UgaXMgaG93IHNpbWlsYXIgdGhlIHNpdHVhdGlvbiBpcyBpbiB0aGUgVVMsIGJ1dCBqdXN0IG1vcmUgdW5kZXIgd3JhcHMuIA0KDQpJbGx1c3RyYXRpbmcgdGhhdCB0aGUgYmVnaW5uaW5nIHN0ZXBzIHRvIHRob3NlIGZ1dHVyZXMgYXJlIGFscmVhZHkgYmVpbmcgdGFrZW4gZG9tZXN0aWNhbGx5IGlzIGFsc28gc2NhcnkuIE9uZSBleGFtcGxlIG9mIHRoaXMgaXMgdGhlIGxhcmdlIG51bWJlciBvZiBwZW9wbGUgaW4gdGhlIFVTIChvdmVyIDExNyBtaWxsaW9uKSB0aGF0IGhhdmUgdGhlaXIgZmFjZSBpbiBhIGZhY2lhbCByZWNvZ25pdGlvbiBuZXR3b3JrIHRoYXQgY2FuIGJlIHNlYXJjaGVkIGJ5IHBvbGljZSB1bndhcnJhbnRlZCwgZXZlbiB3aXRob3V0IGFueSBhY2N1cmFjeSBhdWRpdHMuIFdpdGggdGhlIHBvc3NpYmlsaXR5IG9mIHN0YXRlIHN1cnZlaWxsYW5jZSB0aHJvdWdoIHN1Y2ggc29mdHdhcmUgYmVjb21pbmcgYSBwcmluY2lwYWwgYXNzZXQgZm9yIGFuIGF1dGhvcml0YXJpYW4gcmVnaW1lIGxpZXMgb24gb25lIHNpZGUsIGFuZCBjb3Jwb3JhdGUgc3VydmVpbGxhbmNlIHRoYXQgZGlzcmVnYXJkcyBhbnkgaWRlYSBvZiBwcml2YWN5IG9uIHRoZSBvdGhlci0tQUkgdGVjaG5vbG9neSBpcyBxdWl0ZSBsaXRlcmFsbHkgYSB3ZWFwb24uDQoNCg0KQ2F0aHkgc3Bva2UgYWJvdXQgdGhlIGFzeW1tZXRyeSBvZiBhbGdvcml0aG1zLCB3aGVyZSB0aGUgcGVvcGxlIHdobyBvd24gdGhlIGNvZGUgZGVwbG95IGl0IG9uIG90aGVycy4gV2hlcmVhcyBhbGdvcml0aG1zIGFyZSB1c2VkIGJ5IHBvd2VyZnVsIHBlb3BsZSBhZ2FpbnN0IHRoZSBwdWJsaWMsIHRoZSBwdWJsaWMgZG9lc27igJl0IGhhdmUgYWxnb3JpdGhtcyB0byB1c2UgYWdhaW5zdCB0aGUgcGVvcGxlIHdpdGggdGhlIGNvZGUgYW5kIHRoZSBwb3dlci4gVGhlIHB1YmxpYyBkb2VzbuKAmXQga25vdyB3aGF0IGFsZ29yaXRobXMgYXJlIGJlaW5nIHVzZWQgb24gdGhlbSwgYW5kIHRoZXJl4oCZcyBubyB3YXkgdG8gZ2V0IGFueSBhY2NvdW50YWJpbGl0eSBvciBhcmd1ZSBiYWNrIGFnYWluc3QgdGhlbS4gSeKAmW0gbm90IHN1cmUgaWYgc29tZXRoaW5nIGxpa2UgdGhpcyBleGlzdHMsIGJ1dCBJIHRoaW5rIHRoZXJlIHNob3VsZCBiZSBhIGNvdW5jaWwsIFVOIGdyb3VwLCBvciBzb21lIG92ZXJzZWVycyB0aGF0IGludmVzdGlnYXRlcyBBSSB1c2FnZSBhbmQgc2V0cyB1cCBzb21lIGxhd3Mgb3IgcmVndWxhdGlvbnMgYWdhaW5zdCBoYXJtZnVsIG9yIGluYWNjdXJhdGUgQUkgdXNhZ2UuIEhvdyBkbyB3ZSBob2xkIGNvbXBhbmllcyBhbmQgdGhlIGdvdmVybm1lbnQgYWNjb3VudGFibGUgZm9yIHdoYXQgQUkgdGhleSB1c2UgYW5kIGhvdyB0aGV5IHVzZSBpdD8gQXMgQUkgdXNhZ2UgaW5jcmVhc2VzIGFuZCBleHBhbmRzLCB0aGUgd29ybGQgc2hvdWxkIHRha2Ugc3RyaWRlcyBpbiBtYWludGFpbmluZyBvciBpbml0aWFsaXppbmcgc29tZSBzb3J0IG9mIGFjY291bnRhYmlsaXR5Lg0KDQoNCg0KUkVNRU1CRVIgVE8gQUREIFlPVVIgR0lUSFVCIExJTksgQVQgVEhFIFRPUCBPRiBUSEUgUEFHRSBBTkQgVU5DT01NRU5UIFRIRSBga25pdHJgIE9QVElPTlMuDQoNCg0K